<template>
  <transition name="fade" v-if="show">
    <div class="scan-main">
      <div class="center">
        <div class="scan-line"></div>
        <video id="videoElement" v-if="!code"></video>
        <div v-else class="scan-success">
          <el-result icon="success" title="扫码成功" />
        </div>
        <canvas id="canvasElement"></canvas>
      </div>
    </div>
  </transition>
</template>

<script>
import JSQR from 'jsqr'
import { app } from '@/main'
export default {
  data() {
    return {
      isSafari: null,
      show: false,
      timer: null,
      code: '',
      stream: null,
    }
  },
  computed: {},
  mounted() {
    const isSafari =
      /^((?!chrome|android).)*safari/i.test(navigator.userAgent) &&
      navigator.vendor &&
      navigator.vendor.indexOf('Apple') > -1
    this.isSafari = isSafari
  },
  methods: {
    reset() {
      this.code = ''
    },

    closeCamera() {
      this.stream.getTracks().forEach(function (track) {
        track.stop() // 停止视频流
      })
      clearInterval(this.timer)
      this.timer = null
      this.show = false
    },
    start() {
      this.show = true

      if (this.isSafari) {
        app
          .$confirm(app.$t('出于安全考虑,苹果用户要求手动点击才能扫码,请点击确认继续扫码'), app.$t('确认信息'), {
            type: 'warning',
            customClass: 'scan-qrcode-alert',
            closeOnClickModal: false,
            showCancelButton: false,
            showClose: false,
            zIndex: 10000,
          })
          .then(() => {
            this.scan()
          })
      } else {
        this.scan()
      }
    },
    // 兼容浏览器获取用户媒体方法
    // constrains 参数用于指定要使用的摄像头和音频设备。success 和 error 参数用于处理成功和失败的情况。
    getUserMedia(constrains, success, error) {
      if (navigator.mediaDevices.getUserMedia) {
        // 最新标准API
        navigator.mediaDevices.getUserMedia(constrains).then(success).catch(error)
      } else if (navigator.webkitGetUserMedia) {
        // webkit内核浏览器
        navigator.webkitGetUserMedia(constrains).then(success).catch(error)
      } else if (navigator.mozGetUserMedia) {
        // Firefox浏览器
        navigator.mozGetUserMedia(constrains).then(success).catch(error)
      } else if (navigator.getUserMedia) {
        // 旧版API
        navigator.getUserMedia(constrains).then(success).catch(error)
      }
    },

    scan() {
      if (
        (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) ||
        navigator.getUserMedia ||
        navigator.webkitGetUserMedia ||
        navigator.mozGetUserMedia
      ) {
        // 调用用户媒体设备，访问摄像头
        this.getUserMedia(
          {
            audio: false,
            video: {
              facingMode: 'environment', // user 前置摄像头 environment 后置摄像头
              width: { ideal: 1280 },
              height: { ideal: 720 },
              frameRate: { ideal: 30 },
              codec: 'video/mp4;codecs="avc1.42E01E"',
            },
          },
          this.success,
          this.error,
        )
      } else {
        this.$message({ type: 'error', message: app.$t('你的浏览器不支持访问用户媒体设备') })
        this.show = false
      }
    },
    success(stream) {
      this.stream = stream
      let self = this
      const videoElement = document.getElementById('videoElement')
      const canvasElement = document.getElementById('canvasElement')
      const canvasContext = canvasElement.getContext('2d')
      videoElement.srcObject = stream
      videoElement.onloadedmetadata = function (e) {
        videoElement.play()
      }
      let scanning = true // 标记是否正在扫描
      self.timer = setInterval(function () {
        if (!scanning) {
          stream.getTracks().forEach(function (track) {
            track.stop() // 停止视频流
          })
          return
        }
        canvasElement.width = videoElement.videoWidth
        canvasElement.height = videoElement.videoHeight
        canvasContext.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height)
        const imageData = canvasContext.getImageData(0, 0, canvasElement.width, canvasElement.height)
        const { data } = JSQR(imageData.data, imageData.width, imageData.height) || {}
        if (data) {
          scanning = false // 扫描成功后将 scanning 置为 false，停止扫描
          self.code = data
          // 只提交一次 防止触发多次
          self.$emit('code', data)
          self.$off('code')
          setTimeout(() => {
            self.closeCamera()
          }, 300)
        }
      }, 1000)
    },
    error(e) {
      alert(1)
      this.$message({ type: 'error', message: e })
    },
  },
}
</script>
<style scoped lang="scss">
.scan-main {
  position: relative;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 1);
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 1);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 9999;
  outline: none;
  width: 100%;
  height: 100%;
}

.center {
  width: 200px;
  height: 200px;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

#canvasElement {
  display: none;
  width: 300px;
  height: 300px;
  object-fit: cover;
}

#videoElement {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border: 1px solid red;
}
.scan-success {
  width: 200px;
  height: 200px;
  border-radius: 5px;
  background: #efeeee;
  display: flex;
  align-items: center;
  justify-content: center;
}

.scan-line {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 2px;
  background: linear-gradient(to right, transparent, #00a0e9, transparent);
  animation: scan 2s linear infinite;
}

@keyframes scan {
  0% {
    top: 0;
  }
  100% {
    top: 200px;
  }
}
</style>
